package keer.httpinterface.proxy;

import keer.httpinterface.HttpFile;
import keer.httpinterface.http.ContentTypes;
import keer.httpinterface.http.HttpBody;
import keer.httpinterface.http.HttpContentType;
import keer.httpinterface.http.HttpParams;
import keer.httpinterface.utils.IOUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
 * 多部分表单数据内容类型的身体阅读器类
 * 继承自 ContentTypeBodyReader 类，专门用于处理Content-Type为multipart/form-data类型的请求参数
 * @see ContentTypeBodyReader
 * @see HttpContentType
 * @see HttpParams
 * @see HttpBody
 *
 * @author Keer
 * @since 1.0.0
 */
public class MultipartContentTypeBodyReader extends ContentTypeBodyReader {
    private static final String BOUNDARY_PREFIX = "------HttpInterfaceBoundary";
    private static final String BOUNDARY_SUFFIX = "--";
    private static final String LINE_SEPARATOR = "\r\n";
    /**
     * 读取多部分表单数据内容类型的 HTTP 消息体
     *
     * @param contentType HTTP 消息的内容类型
     * @param params 请求参数
     * @return HttpBody 对象，表示读取的 HTTP 消息体
     * @throws Exception 如果读取过程中发生错误
     */
    @Override
    protected HttpBody read0(HttpContentType contentType, HttpParams params) throws Exception {
        String boundary = BOUNDARY_PREFIX + Long.toHexString(System.currentTimeMillis());
        String boundaryLine =  boundary + LINE_SEPARATOR;
        contentType.setParameter("boundary", boundary);
        String charset = contentType.getCharset();
        MultipartInputStream mis = new MultipartInputStream();
        for (String key : params.allParameters().keySet()) {
            Object[] ps = params.getParameters(key);
            if (ps != null) {
                for (Object p : ps) {
                    if (p == null) {
                        continue;
                    }
                    mis.append(boundaryLine);
                    if (p instanceof HttpFile) {
                        mis.append("Content-Disposition: form-data; name=\"" + key + "\"" + " filename=\"" + ((HttpFile) p).getFilename() + "\"" + LINE_SEPARATOR);
                        mis.append("Content-Type: application/octet-stream" + LINE_SEPARATOR);
                        mis.append(LINE_SEPARATOR);
                        mis.flush(charset);
                        mis.append(((HttpFile) p).getInput());
                        mis.append(LINE_SEPARATOR);
                        mis.flush(charset);
                    } else if (p.getClass().isArray()) {
                        for (int i = 0; i < Array.getLength(p); i++) {
                            append(key, Array.get(p, i), charset, mis);
                        }
                    } else {
                        append(key, p, charset, mis);
                    }
                }
            }
            mis.append(BOUNDARY_PREFIX + boundary + BOUNDARY_SUFFIX + LINE_SEPARATOR);
            mis.flush(charset);
        }
        return HttpBody.streamBody(contentType, mis);
    }

    private void append(String key, Object p, String charset, MultipartInputStream mis) throws UnsupportedEncodingException {
        mis.append("Content-Disposition: form-data; name=\"" + key + "\"" + LINE_SEPARATOR);
//        mis.append("Content-Type: text/plain;charset=" + charset + LINE_SEPARATOR);
        mis.append(LINE_SEPARATOR);
        mis.append(p.toString());
        mis.append(LINE_SEPARATOR);
        mis.flush(charset);
    }

    /**
     * 获取此阅读器支持的内容类型
     *
     * @return 支持的内容类型字符串，此处返回多部分表单数据内容类型
     */
    @Override
    protected String contentType() {
        return ContentTypes.MULTIPART_FORM_DATA;
    }

    private static class MultipartInputStream extends InputStream {
        private final List<InputStream> inputStreams = new ArrayList<>();
        private volatile int index = 0;
        private StringBuilder sb = new StringBuilder();
        @Override
        public int read() throws IOException {
            if (index >= inputStreams.size()) {
                return -1;
            }
            InputStream is = inputStreams.get(index);
            int c = is.read();
            if (c >= 0) {
                return c;
            }
            index++;
            return read();
        }

        private void append(String content) {
            sb.append(content);
        }

        private void flush(String charset) throws UnsupportedEncodingException {
            inputStreams.add(IOUtils.toStream(sb.toString(), charset));
            System.out.println(sb);
            sb.setLength(0);
        }

        private void append(InputStream in) {
            inputStreams.add(in);
        }
    }
}
